package ElectricCraft.frames;

import ElectricCraft.misc.Util;
import ElectricCraft.model.ModelExecutionState;
import ElectricCraft.model.ModelStateBuilder;

import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;

/**
 * DebugPane class.
 */
public class DebugPane extends JPanel {
    private static final int INPUT_BUTTONS_COUNT = 16;

    private final MainWindow parent;
    public DebugPane(MainWindow parent) {
        this.parent = parent;

        initUI();
    }

    private JButton startBtn;
    private JButton stepBtn;
    private JButton runBtn;
    private JButton execBtn;
    private JButton stopBtn;

    private final List<JButton> inputsButtons = new LinkedList<JButton>();

    private Timer timer;
    private Color defaultBtnBackground;
    private Color defaultBtnForeground;
    private void initUI() {
        Box panel = Box.createHorizontalBox();

        startBtn = Util.createBasicButton("\u25BA", "Start");
        panel.add(startBtn);
        startBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                processStart();
            }
        });

        stepBtn = Util.createBasicButton(" > ", "Step");
        panel.add(stepBtn);
        stepBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        processStep();
                    }
                });

        runBtn = Util.createBasicButton(">>", "Run");
        panel.add(runBtn);
        runBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        processRun();
                    }
                });

        execBtn = Util.createBasicButton(">>>", "Execute");
        panel.add(execBtn);
        execBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        processExec();
                    }
                });

        stopBtn = Util.createBasicButton("\u25A0", "Stop");
        panel.add(stopBtn);
        stopBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        processStop();
                    }
                });

        defaultBtnBackground = startBtn.getBackground();
        defaultBtnForeground = startBtn.getForeground();

        panel.add(Box.createHorizontalStrut(20));

        for (int k = 0; k < INPUT_BUTTONS_COUNT; ++k) {
            final JButton inputBtn = Util.createBasicButton(Integer.toString(k + 1), "Power input");
            inputBtn.setVisible(false);

            final int index = k;
            inputBtn.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    processInput(index);
                }
            });

            inputsButtons.add(inputBtn);
            panel.add(inputBtn);
        }

        setLayout(new BorderLayout());
        add(panel, BorderLayout.CENTER);

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
            public boolean dispatchKeyEvent(KeyEvent e) {
                return processKey(e);
            }
        });

        timer = new Timer(50, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                processTimer();
            }
        });
        timer.setRepeats(true);

        updateEnabled(false, false);
    }

    private final boolean [] pressCache = new boolean[10];
    private boolean processKey(KeyEvent e) {
        boolean isPressed = e.getID() == KeyEvent.KEY_PRESSED;
        if (!isPressed && e.getID() != KeyEvent.KEY_RELEASED) {
            return false;
        }

        // not a 0-9 number
        int keyCode = e.getKeyCode();
        if (keyCode < KeyEvent.VK_0 || keyCode > KeyEvent.VK_9) {
            return false;
        }

        // already pressed
        int index = (keyCode != KeyEvent.VK_0) ? keyCode - KeyEvent.VK_1 : 10;
        if (isPressed && pressCache[index]) {
            return false;
        }
        pressCache[index] = isPressed;

        // there is no editor
        if (editor == null || state == null) {
            return false;
        }

        // there is no such input
        if (index >= state.getInputsCount() || state.inputs[index] == -1) {
            return false;
        }

        if (!isRunning) {
            // invert by press
            if (isPressed) {
                state.setInput(index, !state.getInput(index));

                updateInputButtons(state);
                return true;
            }
        } else {
            // active while pressed
            state.setInput(index, isPressed);

            updateInputButtons(state);
            return true;
        }

        return false;
    }

    private void updateEnabled(boolean start, boolean stop) {
        startBtn.setEnabled(start);
        stepBtn.setEnabled(stop);
        runBtn.setEnabled(stop);
        execBtn.setEnabled(stop);
        stopBtn.setEnabled(stop);
    }

    public void updateInputButtons(ModelExecutionState state) {
        int count = (state != null) ? state.getInputsCount() : 0;

        // TODO: mark current state ("step", "run", "exec") by color

        for (int k = 0; k < INPUT_BUTTONS_COUNT; ++k) {
            JButton btn = inputsButtons.get(k);

            boolean isVisible = false;
            if (state != null && k < count) {
                isVisible = state.inputs[k] != -1;
            }

            btn.setVisible(isVisible);

            if (state != null && isVisible) {
                btn.setBackground(state.getInput(k) ? Color.DARK_GRAY : defaultBtnBackground);
                btn.setForeground(state.getInput(k) ? Color.WHITE : defaultBtnForeground);
            }
        }
    }

    private final Map<ChipEditorPane, ModelExecutionState> stateMap = new HashMap<ChipEditorPane, ModelExecutionState>();

    private boolean isRunning = false;
    private final Set<ChipEditorPane> runningEditors = new HashSet<ChipEditorPane>();
    private void enableRunning(boolean enable) {
        if (editor == null || isRunning == enable) {
            return ;
        }

        if (enable && runningEditors.isEmpty()) {
            timer.start();
        } else if (!enable && runningEditors.size() == 1) {
            timer.stop();
        }

        if (enable) {
            assert state != null;
            runningEditors.add(editor);
        } else {
            runningEditors.remove(editor);
        }

        isRunning = enable;
    }

    private void processInput(int index) {
        if (state == null) {
            return ;
        }

        if (index >= state.getInputsCount()) {
            return ;
        }

        boolean value = !state.getInput(index);
        state.setInput(index, value);

        updateInputButtons(state);
    }

    private void processTimer() {
        for (ChipEditorPane editor : runningEditors) {
            ModelExecutionState state = stateMap.get(editor);

            state.step();
        }

        if (isRunning) {
            this.editor.repaint();
        }
    }

    private void processStart() {
        if (editor == null) {
            return ;
        }

        enableRunning(false);

        editor.setReadonly(true);
        updateEnabled(true, true);

        ChipViewerPane.ModelPath path = editor.getModelPath();
        path.getRoot().setModelState(null); // reset model state for full hierarchy

        state = ModelStateBuilder.build(editor.getModel());
        stateMap.put(editor, state);

        updateInputButtons(state);

        editor.repaint();
    }

    private void processStep() {
        if (state == null) {
            return ;
        }

        enableRunning(false);
        state.step();
        editor.repaint();
    }

    private void processRun() {
        // TODO: compile if there is no execution state
        if (state == null) {
            return ;
        }

        enableRunning(true);
    }

    private void processExec() {
        processRun();
    }

    private void processStop() {
        if (state == null) {
            return ;
        }

        editor.setReadonly(false);
        updateEnabled(true, false);
        updateInputButtons(null);

        editor.getModel().setModelState(null);

        enableRunning(false);
        stateMap.remove(editor);

        editor.repaint();
    }

    private ChipEditorPane editor;
    private ModelExecutionState state;
    public void processCurrentEditorChanged() {
        editor = parent.getCurrentEditor();
        state = null;
        if (editor == null) {
            updateEnabled(false, false);
            updateInputButtons(null);
            return ;
        }

        state = stateMap.get(editor);

        updateEnabled(true, state != null);
        updateInputButtons(state);
        isRunning = runningEditors.contains(editor);
    }
}
